/**
 * *****************************************************************************
 * Copyright 2011 See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *****************************************************************************
 */
package sg.atom2d.tools.fx.particle.components;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;

import javax.swing.JPanel;

class Chart extends JPanel {

    static private final int POINT_SIZE = 6;
    static private final int POINT_SIZE_EXPANDED = 10;
    ArrayList<Point> points = new ArrayList();
    private int numberHeight;
    int chartX, chartY;
    int chartWidth, chartHeight;
    int maxX, maxY;
    int overIndex = -1;
    int movingIndex = -1;
    boolean isExpanded;
    String title;

    public Chart(String title) {
        this.title = title;

        setLayout(new GridBagLayout());

        addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent event) {
                movingIndex = overIndex;
            }

            public void mouseReleased(MouseEvent event) {
                movingIndex = -1;
            }

            public void mouseClicked(MouseEvent event) {
                if (event.getClickCount() == 2) {
                    if (overIndex <= 0) {
                        return;
                    }
                    points.remove(overIndex);
                    pointsChanged();
                    repaint();
                    return;
                }
                if (movingIndex != -1) {
                    return;
                }
                if (overIndex != -1) {
                    return;
                }
                int mouseX = event.getX();
                int mouseY = event.getY();
                if (mouseX < chartX || mouseX > chartX + chartWidth) {
                    return;
                }
                if (mouseY < chartY || mouseY > chartY + chartHeight) {
                    return;
                }
                Point newPoint = pixelToPoint(mouseX, mouseY);
                int i = 0;
                Point lastPoint = null;
                for (Point point : points) {
                    if (point.x > newPoint.x) {
                        if (Math.abs(point.x - newPoint.x) < 0.001f) {
                            return;
                        }
                        if (lastPoint != null && Math.abs(lastPoint.x - newPoint.x) < 0.001f) {
                            return;
                        }
                        points.add(i, newPoint);
                        overIndex = i;
                        pointsChanged();
                        repaint();
                        return;
                    }
                    lastPoint = point;
                    i++;
                }
                overIndex = points.size();
                points.add(newPoint);
                pointsChanged();
                repaint();
            }
        });
        addMouseMotionListener(new MouseMotionListener() {
            public void mouseDragged(MouseEvent event) {
                if (movingIndex == -1) {
                    return;
                }
                float nextX = movingIndex == points.size() - 1 ? maxX : points.get(movingIndex + 1).x - 0.001f;
                if (movingIndex == 0) {
                    nextX = 0;
                }
                float prevX = movingIndex == 0 ? 0 : points.get(movingIndex - 1).x + 0.001f;
                Point point = points.get(movingIndex);
                point.x = Math.min(nextX, Math.max(prevX, (event.getX() - chartX) / (float) chartWidth * maxX));
                point.y = Math.min(maxY, Math.max(0, chartHeight - (event.getY() - chartY)) / (float) chartHeight * maxY);
                pointsChanged();
                repaint();
            }

            public void mouseMoved(MouseEvent event) {
                int mouseX = event.getX();
                int mouseY = event.getY();
                int oldIndex = overIndex;
                overIndex = -1;
                int pointSize = isExpanded ? POINT_SIZE_EXPANDED : POINT_SIZE;
                int i = 0;
                for (Point point : points) {
                    int x = chartX + (int) (chartWidth * (point.x / (float) maxX));
                    int y = chartY + chartHeight - (int) (chartHeight * (point.y / (float) maxY));
                    if (Math.abs(x - mouseX) <= pointSize && Math.abs(y - mouseY) <= pointSize) {
                        overIndex = i;
                        break;
                    }
                    i++;
                }
                if (overIndex != oldIndex) {
                    repaint();
                }
            }
        });
    }

    public void addPoint(float x, float y) {
        points.add(new Point(x, y));
    }

    public void pointsChanged() {
    }

    public float[] getValuesX() {
        float[] values = new float[points.size()];
        int i = 0;
        for (Point point : points) {
            values[i++] = point.x;
        }
        return values;
    }

    public float[] getValuesY() {
        float[] values = new float[points.size()];
        int i = 0;
        for (Point point : points) {
            values[i++] = point.y;
        }
        return values;
    }

    public void setValues(float[] x, float[] y) {
        points.clear();
        for (int i = 0; i < x.length; i++) {
            points.add(new Point(x[i], y[i]));
        }
    }

    Point pixelToPoint(float x, float y) {
        Point point = new Point();
        point.x = Math.min(maxX, Math.max(0, x - chartX) / (float) chartWidth * maxX);
        point.y = Math.min(maxY, Math.max(0, chartHeight - (y - chartY)) / (float) chartHeight * maxY);
        return point;
    }

    Point pointToPixel(Point point) {
        Point pixel = new Point();
        pixel.x = chartX + (int) (chartWidth * (point.x / (float) maxX));
        pixel.y = chartY + chartHeight - (int) (chartHeight * (point.y / (float) maxY));
        return pixel;
    }

    protected void paintComponent(Graphics graphics) {
        // setOpaque(true);
        // setBackground(Color.red);
        super.paintComponent(graphics);

        Graphics2D g = (Graphics2D) graphics;
        FontMetrics metrics = g.getFontMetrics();
        if (numberHeight == 0) {
            numberHeight = getFont().layoutGlyphVector(g.getFontRenderContext(), new char[]{'0'}, 0, 1, Font.LAYOUT_LEFT_TO_RIGHT)
                    .getGlyphPixelBounds(0, g.getFontRenderContext(), 0, 0).height;
        }

        int width = getWidth();
        if (!isExpanded) {
            width = Math.min(150, width);
        }
        width = Math.max(100, width);
        int height = getHeight();
        int maxAxisLabelWidth;
        int yAxisWidth;
        if (isExpanded) {
            maxAxisLabelWidth = metrics.stringWidth("100%");
            yAxisWidth = maxAxisLabelWidth + 8;
            chartX = yAxisWidth;
            chartY = numberHeight / 2 + 1;
            chartWidth = width - yAxisWidth - 2;
            chartHeight = height - chartY - numberHeight - 8;
        } else {
            maxAxisLabelWidth = 0;
            yAxisWidth = 2;
            chartX = yAxisWidth;
            chartY = 2;
            chartWidth = width - yAxisWidth - 2;
            chartHeight = height - chartY - 3;
        }

        g.setColor(Color.white);
        g.fillRect(chartX, chartY, chartWidth, chartHeight);
        g.setColor(Color.black);
        g.drawRect(chartX, chartY, chartWidth, chartHeight);

        maxX = 1;
        {
            int y = height;
            if (isExpanded) {
                y -= numberHeight;
            } else {
                y += 5;
            }
            int xSplit = (int) Math.min(10, chartWidth / (maxAxisLabelWidth * 1.5f));
            for (int i = 0; i <= xSplit; i++) {
                float percent = i / (float) xSplit;
                String label = axisLabel(maxX * percent);
                int labelWidth = metrics.stringWidth(label);
                int x = (int) (yAxisWidth + chartWidth * percent);
                if (i != 0 && i != xSplit) {
                    g.setColor(Color.lightGray);
                    g.drawLine(x, chartY + 1, x, chartY + chartHeight);
                    g.setColor(Color.black);
                }
                g.drawLine(x, y - 4, x, y - 8);
                if (isExpanded) {
                    x -= labelWidth / 2;
                    if (i == xSplit) {
                        x = Math.min(x, width - labelWidth);
                    }
                    g.drawString(label, x, y + numberHeight);
                }
            }
        }

        maxY = 1;
        {
            int ySplit = isExpanded ? Math.min(10, chartHeight / (numberHeight * 3)) : 4;
            for (int i = 0; i <= ySplit; i++) {
                float percent = i / (float) ySplit;
                String label = axisLabel(maxY * percent);
                int labelWidth = metrics.stringWidth(label);
                int y = (int) (chartY + chartHeight - chartHeight * percent);
                if (isExpanded) {
                    g.drawString(label, yAxisWidth - 6 - labelWidth, y + numberHeight / 2);
                }
                if (i != 0 && i != ySplit) {
                    g.setColor(Color.lightGray);
                    g.drawLine(chartX, y, chartX + chartWidth - 1, y);
                    g.setColor(Color.black);
                }
                g.drawLine(yAxisWidth - 4, y, yAxisWidth, y);
            }
        }

        {
            int titleWidth = metrics.stringWidth(title);
            int x = yAxisWidth + chartWidth / 2 - titleWidth / 2;
            int y = chartY + chartHeight / 2 - numberHeight / 2;
            g.setColor(Color.white);
            g.fillRect(x - 2, y - 2, titleWidth + 4, numberHeight + 4);
            g.setColor(Color.lightGray);
            g.drawString(title, x, y + numberHeight);
        }

        g.setColor(Color.blue);
        g.setStroke(new BasicStroke(isExpanded ? 3 : 2));
        int lastX = -1, lastY = -1;
        for (Point point : points) {
            Point pixel = pointToPixel(point);
            if (lastX != -1) {
                g.drawLine(lastX, lastY, (int) pixel.x, (int) pixel.y);
            }
            lastX = (int) pixel.x;
            lastY = (int) pixel.y;
        }
        g.drawLine(lastX, lastY, chartX + chartWidth - 1, lastY);
        for (int i = 0, n = points.size(); i < n; i++) {
            Point point = points.get(i);
            Point pixel = pointToPixel(point);
            if (overIndex == i) {
                g.setColor(Color.red);
            } else {
                g.setColor(Color.black);
            }
            String label = valueLabel(point.y);
            int labelWidth = metrics.stringWidth(label);
            int pointSize = isExpanded ? POINT_SIZE_EXPANDED : POINT_SIZE;
            int x = (int) pixel.x - pointSize / 2;
            int y = (int) pixel.y - pointSize / 2;
            g.fillOval(x, y, pointSize, pointSize);
            if (isExpanded) {
                g.setColor(Color.black);
                x = Math.max(chartX + 2, Math.min(chartX + chartWidth - labelWidth, x));
                y -= 3;
                if (y < chartY + numberHeight + 3) {
                    y += 27;
                } else if (n > 1) {
                    Point comparePoint = i == n - 1 ? points.get(i - 1) : points.get(i + 1);
                    if (y < chartY + chartHeight - 27 && comparePoint.y > point.y) {
                        y += 27;
                    }
                }
                g.drawString(label, x, y);
            }
        }
    }

    private String valueLabel(float value) {
        value = (int) (value * 1000) / 10f;
        if (value % 1 == 0) {
            return String.valueOf((int) value) + '%';
        } else {
            return String.valueOf(value) + '%';
        }
    }

    private String axisLabel(float value) {
        value = (int) (value * 100);
        if (value % 1 == 0) {
            return String.valueOf((int) value) + '%';
        } else {
            return String.valueOf(value) + '%';
        }
    }

    static public class Point {

        public float x;
        public float y;

        public Point() {
        }

        public Point(float x, float y) {
            this.x = x;
            this.y = y;
        }
    }

    public boolean isExpanded() {
        return isExpanded;
    }

    public void setExpanded(boolean isExpanded) {
        this.isExpanded = isExpanded;
    }
}
